/* w_tk.c
 *
 * Micropolis, Unix Version.  This game was released for the Unix platform
 * in or about 1990 and has been modified for inclusion in the One Laptop
 * Per Child program.  Copyright (C) 1989 - 2007 Electronic Arts Inc.  If
 * you need assistance with this program, you may contact:
 *   http://wiki.laptop.org/go/Micropolis  or email  micropolis@laptop.org.
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or (at
 * your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.  You should have received a
 * copy of the GNU General Public License along with this program.  If
 * not, see <http://www.gnu.org/licenses/>.
 * 
 *             ADDITIONAL TERMS per GNU GPL Section 7
 * 
 * No trademark or publicity rights are granted.  This license does NOT
 * give you any right, title or interest in the trademark SimCity or any
 * other Electronic Arts trademark.  You may not distribute any
 * modification of this program using the trademark SimCity or claim any
 * affliation or association with Electronic Arts Inc. or its employees.
 * 
 * Any propagation or conveyance of this program must include this
 * copyright notice and these terms.
 * 
 * If you convey this program (or any modifications of it) and assume
 * contractual liability for the program to recipients of it, you agree
 * to indemnify Electronic Arts for any liability that those contractual
 * assumptions impose on Electronic Arts.
 * 
 * You may not misrepresent the origins of this program; modified
 * versions of the program must be marked as such and not identified as
 * the original program.
 * 
 * This disclaimer supplements the one included in the General Public
 * License.  TO THE FULLEST EXTENT PERMISSIBLE UNDER APPLICABLE LAW, THIS
 * PROGRAM IS PROVIDED TO YOU "AS IS," WITH ALL FAULTS, WITHOUT WARRANTY
 * OF ANY KIND, AND YOUR USE IS AT YOUR SOLE RISK.  THE ENTIRE RISK OF
 * SATISFACTORY QUALITY AND PERFORMANCE RESIDES WITH YOU.  ELECTRONIC ARTS
 * DISCLAIMS ANY AND ALL EXPRESS, IMPLIED OR STATUTORY WARRANTIES,
 * INCLUDING IMPLIED WARRANTIES OF MERCHANTABILITY, SATISFACTORY QUALITY,
 * FITNESS FOR A PARTICULAR PURPOSE, NONINFRINGEMENT OF THIRD PARTY
 * RIGHTS, AND WARRANTIES (IF ANY) ARISING FROM A COURSE OF DEALING,
 * USAGE, OR TRADE PRACTICE.  ELECTRONIC ARTS DOES NOT WARRANT AGAINST
 * INTERFERENCE WITH YOUR ENJOYMENT OF THE PROGRAM; THAT THE PROGRAM WILL
 * MEET YOUR REQUIREMENTS; THAT OPERATION OF THE PROGRAM WILL BE
 * UNINTERRUPTED OR ERROR-FREE, OR THAT THE PROGRAM WILL BE COMPATIBLE
 * WITH THIRD PARTY SOFTWARE OR THAT ANY ERRORS IN THE PROGRAM WILL BE
 * CORRECTED.  NO ORAL OR WRITTEN ADVICE PROVIDED BY ELECTRONIC ARTS OR
 * ANY AUTHORIZED REPRESENTATIVE SHALL CREATE A WARRANTY.  SOME
 * JURISDICTIONS DO NOT ALLOW THE EXCLUSION OF OR LIMITATIONS ON IMPLIED
 * WARRANTIES OR THE LIMITATIONS ON THE APPLICABLE STATUTORY RIGHTS OF A
 * CONSUMER, SO SOME OR ALL OF THE ABOVE EXCLUSIONS AND LIMITATIONS MAY
 * NOT APPLY TO YOU.
 */
#include "sim.h"

#ifdef MSDOS
#    define filename2UNIX(name)	\
    {   char *p; for (p = name; *p; p++) if (*p == '\\') *p = '/'; }
#else
#    define filename2UNIX(name) /**/
#endif
    Tcl_Interp * tk_mainInterp = NULL;
Tcl_CmdBuf buffer = NULL;
Tk_TimerToken sim_timer_token = 0;
int sim_timer_idle = 0;
int sim_timer_set = 0;
Tk_Window MainWindow;
int UpdateDelayed = 0;
int AutoScrollEdge = 16;
int AutoScrollStep = 16;
int AutoScrollDelay = 10;
Tk_TimerToken earthquake_timer_token;
int earthquake_timer_set = 0;
int earthquake_delay = 3000;
int PerformanceTiming;
double FlushTime;
int NeedRest = 0;

#define DEF_VIEW_FONT	"-Adobe-Helvetica-Bold-R-Normal-*-140-*"

Tk_ConfigSpec TileViewConfigSpecs[] = {
    {TK_CONFIG_FONT, "-font", (char *) NULL, (char *) NULL,
     DEF_VIEW_FONT, Tk_Offset(SimView, fontPtr), 0},
    {TK_CONFIG_STRING, "-messagevar", (char *) NULL, (char *) NULL,
     NULL, Tk_Offset(SimView, message_var), 0},
    {TK_CONFIG_PIXELS, "-width", "width", "Width",
     0, Tk_Offset(SimView, width), 0},
    {TK_CONFIG_PIXELS, "-height", "height", "Height",
     0, Tk_Offset(SimView, height), 0},
    {TK_CONFIG_END, (char *) NULL, (char *) NULL, (char *) NULL,
     (char *) NULL, 0, 0}
};

int TileViewCmd(CLIENT_ARGS);
static void TileViewEventProc(ClientData clientData, XEvent * eventPtr);
static void DestroyTileView(ClientData clientData);
static void MicropolisTimerProc(ClientData clientData);

int SimCmd(CLIENT_ARGS);
int DoEditorCmd(CLIENT_ARGS);
int DoMapCmd(CLIENT_ARGS);
int GraphViewCmd(CLIENT_ARGS);
int DoGraphCmd(CLIENT_ARGS);
int SpriteCmd(CLIENT_ARGS);
extern int Tk_PieMenuCmd();
extern int Tk_IntervalCmd();

int TileViewCmd(CLIENT_ARGS)
{
    Tk_Window tkwin = (Tk_Window) clientData;
    SimView *view;
    int viewclass;

    if(argc < 2)
    {
        Tcl_AppendResult(interp, "wrong # args:  should be \"", argv[0], " pathName ?options?\"", (char *) NULL);
        return TCL_ERROR;
    }

    if(strcmp(argv[0], "editorview") == 0)
        viewclass = Editor_Class;
    else if(strcmp(argv[0], "mapview") == 0)
        viewclass = Map_Class;
    else
    {
        return TCL_ERROR;
    }

    tkwin = Tk_CreateWindowFromPath(interp, tkwin, argv[1], (char *) NULL);
    if(tkwin == NULL)
    {
        return TCL_ERROR;
    }

    view = (SimView *) ckalloc(sizeof(SimView));

    view->tkwin = tkwin;
    view->interp = interp;
    view->flags = 0;

    if(viewclass == Editor_Class)
    {
        Tk_SetClass(view->tkwin, "EditorView");

        Tk_CreateEventHandler(view->tkwin,
                              VisibilityChangeMask |
                              ExposureMask |
                              StructureNotifyMask |
                              EnterWindowMask |
                              LeaveWindowMask | PointerMotionMask, TileViewEventProc, (ClientData) view);
        Tcl_CreateCommand(interp, Tk_PathName(view->tkwin), DoEditorCmd, (ClientData) view, (void (*)()) NULL);
    }
    else
    {
        Tk_SetClass(view->tkwin, "MapView");

        Tk_CreateEventHandler(view->tkwin, VisibilityChangeMask | ExposureMask | StructureNotifyMask    /* |
                                                                                                         * EnterWindowMask |
                                                                                                         * LeaveWindowMask |
                                                                                                         * PointerMotionMask */ ,
                              TileViewEventProc, (ClientData) view);
        Tcl_CreateCommand(interp, Tk_PathName(view->tkwin), DoMapCmd, (ClientData) view, (void (*)()) NULL);
    }

    Tk_MakeWindowExist(view->tkwin);

    if(getenv("XSYNCHRONIZE") != NULL)
    {
        XSynchronize(Tk_Display(tkwin), 1);
    }

    if(viewclass == Editor_Class)
    {
        InitNewView(view, "MicropolisEditor", Editor_Class, EDITOR_W, EDITOR_H);
        DoNewEditor(view);
    }
    else
    {
        InitNewView(view, "MicropolisMap", Map_Class, MAP_W, MAP_H);
        DoNewMap(view);
    }

    if(ConfigureTileView(interp, view, argc - 2, argv + 2, 0) != TCL_OK)
    {
        /*
         * XXX: destroy view 
         */
        Tk_DestroyWindow(view->tkwin);
        return TCL_ERROR;
    }

    switch (view->class)
    {
        case Editor_Class:
            break;
        case Map_Class:
            view->invalid = 1;
            view->update = 1;
            DoUpdateMap(view);
            break;
    }

    interp->result = Tk_PathName(view->tkwin);
    return TCL_OK;
}

int ConfigureTileView(Tcl_Interp * interp, SimView * view, int argc, char **argv, int flags)
{
    if(Tk_ConfigureWidget(interp, view->tkwin, TileViewConfigSpecs, argc, argv, (char *) view, flags) != TCL_OK)
    {
        return TCL_ERROR;
    }

    if(view->class == Map_Class)
    {
        Tk_GeometryRequest(view->tkwin, MAP_W, MAP_H);
    }
    else
    {
        if(view->width || view->height)
        {
            Tk_GeometryRequest(view->tkwin, view->width, view->height);
        }
    }
    EventuallyRedrawView(view);
    return TCL_OK;
}

void InvalidateMaps(void)
{
    SimView *view;

//fprintf(stderr, "InvalidateMaps\n");
    for(view = sim->map; view != NULL; view = view->next)
    {
        view->invalid = 1;
        view->skip = 0;
        EventuallyRedrawView(view);
    }
    sim_skip = 0;
}

void InvalidateEditors(void)
{
    SimView *view;

//fprintf(stderr, "InvalidateEditors\n");
    for(view = sim->editor; view != NULL; view = view->next)
    {
        view->invalid = 1;
        view->skip = 0;
        EventuallyRedrawView(view);
    }
    sim_skip = 0;
}

void RedrawMaps(void)
{
    SimView *view;

//fprintf(stderr, "RedrawMaps\n");

    for(view = sim->map; view != NULL; view = view->next)
    {
        view->skip = 0;
        EventuallyRedrawView(view);
    }
    sim_skip = 0;
}

void RedrawEditors(void)
{
    SimView *view;

//fprintf(stderr, "RedrawEditors\n");

    for(view = sim->editor; view != NULL; view = view->next)
    {
        view->skip = 0;
        EventuallyRedrawView(view);
    }
    sim_skip = 0;
}

static void DisplayTileView(ClientData clientData)
{
    SimView *view = (SimView *) clientData;
    Tk_Window tkwin = view->tkwin;

    view->flags &= ~VIEW_REDRAW_PENDING;
    if(view->visible && (tkwin != NULL) && Tk_IsMapped(tkwin))
    {
        switch (view->class)
        {
            case Editor_Class:
                view->skip = 0;
                view->update = 1;
                DoUpdateEditor(view);
                break;
            case Map_Class:
//fprintf(stderr, "DisplayTileView\n");
                view->skip = 0;
                view->update = 1;
                DoUpdateMap(view);
                break;
        }
    }
}

/* comefrom:
    ConfigureTileView
    TileViewEventProc expose configure motion
    InvalidateMaps
    EraserTo
    DoSetMapState
    AddInk
    EraserTo
 */

void EventuallyRedrawView(SimView * view)
{
    if(!(view->flags & VIEW_REDRAW_PENDING))
    {
        Tk_DoWhenIdle(DisplayTileView, (ClientData) view);
        view->flags |= VIEW_REDRAW_PENDING;
    }

}

void CancelRedrawView(SimView * view)
{
    if(view->flags & VIEW_REDRAW_PENDING)
    {
        Tk_CancelIdleCall(DisplayTileView, (ClientData) view);
    }
    view->flags &= ~VIEW_REDRAW_PENDING;
}

static void TileAutoScrollProc(ClientData clientData)
{
    SimView *view = (SimView *) clientData;
    char buf[256];

    if(view->tool_mode != 0)
    {
        int dx = 0, dy = 0;
        int root_x, root_y, x, y;
        unsigned int key_buttons;
        Window root, child;

        XQueryPointer(Tk_Display(view->tkwin), Tk_WindowId(view->tkwin),
                      &root, &child, &root_x, &root_y, &x, &y, &key_buttons);

        if(x < AutoScrollEdge)
            dx = -AutoScrollStep;
        else if(x > (view->w_width - AutoScrollEdge))
            dx = AutoScrollStep;
        if(y < AutoScrollEdge)
            dy = -AutoScrollStep;
        else if(y > (view->w_height - AutoScrollEdge))
            dy = AutoScrollStep;

        if(dx || dy)
        {
            int px = view->pan_x, py = view->pan_y;

            if(view->tool_mode == -1)
            {
                dx = -dx;
                dy = -dy;
            }

            DoPanBy(view, dx, dy);
            view->tool_x += view->pan_x - px;
            view->tool_y += view->pan_y - py;
            view->auto_scroll_token = Tk_CreateTimerHandler(AutoScrollDelay, TileAutoScrollProc, (ClientData) view);

            sprintf(buf, "UIDidPan %s %d %d", Tk_PathName(view->tkwin), x, y);
            Eval(buf);
        }
    }
}

static void TileViewEventProc(ClientData clientData, XEvent * eventPtr)
{
    SimView *view = (SimView *) clientData;

    if((eventPtr->type == Expose) && (eventPtr->xexpose.count == 0))
    {
        view->visible = 1;
        EventuallyRedrawView(view);
    }
    else if(eventPtr->type == MapNotify)
    {
        view->visible = 1;
    }
    else if(eventPtr->type == UnmapNotify)
    {
        view->visible = 0;
    }
    else if(eventPtr->type == VisibilityNotify)
    {
        if(eventPtr->xvisibility.state == VisibilityFullyObscured)
            view->visible = 0;
        else
            view->visible = 1;
    }
    else if(eventPtr->type == ConfigureNotify)
    {
        if(view->class == Editor_Class)
            DoResizeView(view, eventPtr->xconfigure.width, eventPtr->xconfigure.height);
        EventuallyRedrawView(view);
    }
    else if(eventPtr->type == DestroyNotify)
    {
        Tcl_DeleteCommand(view->interp, Tk_PathName(view->tkwin));
        view->tkwin = NULL;
        CancelRedrawView(view);
        Tk_EventuallyFree((ClientData) view, DestroyTileView);
    }
    else if((view->class == Editor_Class) &&
            (view->show_me != 0) &&
            ((eventPtr->type == EnterNotify) || (eventPtr->type == LeaveNotify) || (eventPtr->type == MotionNotify)))
    {
        int last_x = view->tool_x, last_y = view->tool_y, last_showing = view->tool_showing;
        int x, y, showing;

        if(eventPtr->type == EnterNotify)
        {
            showing = 1;
            x = eventPtr->xcrossing.x;
            y = eventPtr->xcrossing.y;
        }
        else if(eventPtr->type == LeaveNotify)
        {
            showing = 0;
            x = eventPtr->xcrossing.x;
            y = eventPtr->xcrossing.y;
        }
        else
        {
            showing = 1;
            x = eventPtr->xmotion.x;
            y = eventPtr->xmotion.y;
        }

        if(view->tool_mode != 0)
        {

            if((x < AutoScrollEdge) ||
               (x > (view->w_width - AutoScrollEdge)) ||
               (y < AutoScrollEdge) || (y > (view->w_height - AutoScrollEdge)))
            {
                if(!view->auto_scroll_token)
                {
                    view->auto_scroll_token =
                        Tk_CreateTimerHandler(AutoScrollDelay, TileAutoScrollProc, (ClientData) view);
                }
            }
            else
            {
                if(view->auto_scroll_token)
                {
                    Tk_DeleteTimerHandler(view->auto_scroll_token);
                    view->auto_scroll_token = 0;
                }
            }
        }

        ViewToPixelCoords(view, x, y, &x, &y);
        view->tool_showing = showing;

        if(view->tool_mode != -1)
        {
            view->tool_x = x;
            view->tool_y = y;
        }

/* XXX: redraw all views showing cursor */
/* XXX: also, make sure switching tools works w/out moving */
        if(((view->tool_showing != last_showing) ||
            ((view->tool_x >> 4) != (last_x >> 4)) || ((view->tool_y >> 4) != (last_y >> 4))))
        {
#if 1
            EventuallyRedrawView(view);
#else
            RedrawEditors();
#endif
        }
    }
}

static void DestroyTileView(ClientData clientData)
{
    SimView *view = (SimView *) clientData;

    DestroyView(view);
}

void StdinProc(ClientData clientData, int mask)
{
    char line[200];
    static int gotPartial = 0;
    char *cmd;
    int result;

    if(mask & TK_READABLE)
    {
        if(fgets(line, 200, stdin) == NULL)
        {
            if(!gotPartial)
            {
                if(sim_tty)
                {
                    sim_exit(0);    // Just sets tkMustExit and ExitReturn
                    return;
                }
                else
                {
                    Tk_DeleteFileHandler(0);
                }
                return;
            }
            else
            {
                line[0] = 0;
            }
        }
        cmd = Tcl_AssembleCmd(buffer, line);
        if(cmd == NULL)
        {
            gotPartial = 1;
            return;
        }
        gotPartial = 0;
        result = Tcl_RecordAndEval(tk_mainInterp, cmd, 0);
        if(*tk_mainInterp->result != 0)
        {
            if((result != TCL_OK) || sim_tty)
            {
                printf("%s\n", tk_mainInterp->result);
            }
        }
        if(sim_tty)
        {
            printf("sim:\n");
            fflush(stdout);
        }
    }
}

static void StructureProc(ClientData clientData, XEvent * eventPtr)
{
    if(eventPtr->type == DestroyNotify)
    {
        MainWindow = NULL;
    }
}

#if 0
static void DelayedMap(ClientData clientData)
{
    while(Tk_DoOneEvent(TK_IDLE_EVENTS) != 0)
    {
        /*
         * Empty loop body. 
         */
    }
    if(MainWindow == NULL)
    {
        return;
    }
    Tk_MapWindow(MainWindow);
}
#endif

void DidStopPan(SimView * view)
{
    char buf[256];

    sprintf(buf, "UIDidStopPan %s", Tk_PathName(view->tkwin));

    Eval(buf);
}

static void MicropolisTimerProc(ClientData clientData)
{
    sim_timer_token = NULL;
    sim_timer_set = 0;

    if(NeedRest > 0)
    {
        NeedRest--;
    }

    if(SimSpeed)
    {
        sim_loop(1);
        StartMicropolisTimer();
    }
    else
    {
        StopMicropolisTimer();
    }
}

void ReallyStartMicropolisTimer(ClientData clientData)
{
    int delay = sim_delay;
    XDisplay *xd = XDisplays;

    StopMicropolisTimer();

    while(xd != NULL)
    {
        if((NeedRest > 0) || ShakeNow || (xd->tkDisplay->buttonWinPtr != NULL) || (xd->tkDisplay->grabWinPtr != NULL))
        {
            if(ShakeNow || NeedRest)
            {
                if(delay < 50000)
                    delay = 50000;
            }
            else
            {
            }
            break;
        }
        xd = xd->next;
    }

    sim_timer_token = Tk_CreateMicroTimerHandler(0, delay, MicropolisTimerProc, (ClientData) 0);

    sim_timer_set = 1;
}

void StartMicropolisTimer(void)
{
    if(sim_timer_idle == 0)
    {
        sim_timer_idle = 1;
        Tk_DoWhenIdle(ReallyStartMicropolisTimer, NULL);
    }
}

void StopMicropolisTimer(void)
{
    if(sim_timer_idle != 0)
    {
        sim_timer_idle = 0;
        Tk_CancelIdleCall(ReallyStartMicropolisTimer, NULL);
    }

    if(sim_timer_set)
    {
        if(sim_timer_token != 0)
        {
            Tk_DeleteTimerHandler(sim_timer_token);
            sim_timer_token = 0;
        }
        sim_timer_set = 0;
    }
}

void FixMicropolisTimer()
{
    if(sim_timer_set)
    {
        StartMicropolisTimer();
    }
}

static void DelayedUpdate(ClientData clientData)
{
//fprintf(stderr, "DelayedUpdate\n");
    UpdateDelayed = 0;
    sim_skip = 0;
    sim_update();
}

void Kick(void)
{
    if(!UpdateDelayed)
    {
        UpdateDelayed = 1;
        Tk_DoWhenIdle(DelayedUpdate, (ClientData) NULL);
    }
}

void StopEarthquake(void)
{
    ShakeNow = 0;
    if(earthquake_timer_set)
    {
        Tk_DeleteTimerHandler(earthquake_timer_token);
    }
    earthquake_timer_set = 0;
}

void DoEarthQuake(void)
{
    MakeSound("city", "Explosion-Low");
    Eval("UIEarthQuake");
    ShakeNow++;
    if(earthquake_timer_set)
    {
        Tk_DeleteTimerHandler(earthquake_timer_token);
    }
    Tk_CreateTimerHandler(earthquake_delay, (void (*)()) StopEarthquake, (ClientData) 0);
    earthquake_timer_set = 1;
}

void StopToolkit(void)
{
    if(tk_mainInterp != NULL)
    {
        Eval("catch {DoStopMicropolis}");
    }
}

int Eval(char *buf)
{
    int result = Tcl_Eval(tk_mainInterp, buf, 0, (char **) NULL);

    if(result != TCL_OK)
    {
        char *errorinfo = Tcl_GetVar(tk_mainInterp, "errorInfo",
                                     TCL_GLOBAL_ONLY);

        if(errorinfo == NULL)
            errorinfo = "<no backtrace>";
        fprintf(stderr, "Micropolis: error in TCL code: %s\n%s\n", tk_mainInterp->result, errorinfo);
    }
    return (result);
}

void tk_main(void)
{
    char initCmd[256];
    Tk_3DBorder border;

    tk_mainInterp = Tcl_CreateExtendedInterp();

#if 0
    /*
     * XXX: Figure out Extended TCL 
     */
    tclAppName = "Wish";
    tclAppLongname = "Wish - Tk Shell";
    tclAppVersion = TK_VERSION;
    Tcl_ShellEnvInit(interp, TCLSH_ABORT_STARTUP_ERR, name, 0, NULL,    /* argv var already set  */
                     fileName == NULL,  /* interactive?          */
                     NULL);     /* Standard default file */
#endif

    MainWindow = Tk_CreateMainWindow(tk_mainInterp, FirstDisplay, "Micropolis");
    if(MainWindow == NULL)
    {
        fprintf(stderr, "%s\n", tk_mainInterp->result);
        sim_really_exit(1);     // Just sets tkMustExit and ExitReturn
    }
    Tk_SetClass(MainWindow, "Tk");
    Tk_CreateEventHandler(MainWindow, StructureNotifyMask, StructureProc, (ClientData) NULL);
/*  Tk_DoWhenIdle(DelayedMap, (ClientData) NULL); */

    Tk_GeometryRequest(MainWindow, 256, 256);
    border = Tk_Get3DBorder(tk_mainInterp, MainWindow, None, "gray75");
    if(border == NULL)
    {
        Tcl_SetResult(tk_mainInterp, (char *) NULL, TCL_STATIC);
        Tk_SetWindowBackground(MainWindow, WhitePixelOfScreen(Tk_Screen(MainWindow)));
    }
    else
    {
        Tk_SetBackgroundFromBorder(MainWindow, border);
    }
    XSetForeground(Tk_Display(MainWindow),
                   DefaultGCOfScreen(Tk_Screen(MainWindow)), BlackPixelOfScreen(Tk_Screen(MainWindow)));

    sim_command_init();
    map_command_init();
    editor_command_init();
    graph_command_init();
    date_command_init();
    sprite_command_init();
    sound_command_init();

#ifdef CAM
    cam_command_init();
#endif

    Tcl_CreateCommand(tk_mainInterp, "piemenu", Tk_PieMenuCmd, (ClientData) MainWindow, (void (*)()) NULL);
    Tcl_CreateCommand(tk_mainInterp, "interval", Tk_IntervalCmd, (ClientData) MainWindow, (void (*)()) NULL);

    sim = MakeNewSim();

    sprintf(initCmd, "source %s/micropolis.tcl", ResourceDir);
    filename2UNIX(initCmd);
    if(Eval(initCmd))
    {
        sim_exit(1);            // Just sets tkMustExit and ExitReturn
        goto bail;
    }

    sim_init();

    buffer = Tcl_CreateCmdBuf();

    if(sim_tty)
    {
        Tk_CreateFileHandler(0, TK_READABLE, StdinProc, (ClientData) 0);
    }

    {
        char buf[1024];

        sprintf(buf, "UIStartMicropolis {%s} {%s} {%s}", HomeDir, ResourceDir, HostName);
        filename2UNIX(buf);
        if(Eval(buf) != TCL_OK)
        {
            sim_exit(1);        // Just sets tkMustExit and ExitReturn
            goto bail;
        }
    }

    if(sim_tty)
    {
        printf("sim:\n");
    }
    fflush(stdout);

    Tk_MainLoop();

    sim_really_exit(0);

  bail:

    if(buffer != NULL)
    {
        Tcl_DeleteCmdBuf(buffer);
    }

    Tcl_DeleteInterp(tk_mainInterp);
}
